package com.lemon.core.mybatis.factory;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.lemon.core.common.lang.util.StringUtils;
import com.lemon.core.mybatis.config.datasource.DataSourceDefaultVal;
import com.lemon.core.mybatis.util.PropertiesUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import java.util.*;

/**
 * @author lemon
 * @ClassName CoreBeanFactory
 * @desc
 * @Date 2018/12/15 17:41
 * @Version 1.0
 **/
public class CoreBeanFactory  implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    /**
     * env
     */
    private Environment environment;

    /**
     * resourceLoader
     */
    private ResourceLoader resourceLoader;

    /**
     * db name set
     */
    private Set<String> dbNameSet = new HashSet<String>();

    /**
     * datasource config
     */
    private Map<String, String> dataSourceConfigMap = new HashMap<String, String>();

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;

        PropertySource<?> applicationConfigurationProperties = ((ConfigurableEnvironment) environment).getPropertySources()
                //.get("applicationConfig: [classpath:/application.properties]");
                .get("applicationConfigurationProperties");
        if (applicationConfigurationProperties == null) {
            return;
        }
        List<PropertySource> applicationConfigurationPropertiesSource =
                (List<PropertySource>) applicationConfigurationProperties.getSource();

        if (applicationConfigurationPropertiesSource == null) {
            return;
        }
        for (PropertySource propertySource : applicationConfigurationPropertiesSource) {
            if (!(propertySource.getSource() instanceof Set)) {
                continue;
            }
            Set sourceSet = (Set) propertySource.getSource();
            if (sourceSet == null) {
                continue;
            }

            for (Object o : sourceSet) {
                if (o instanceof MapPropertySource) {
                    Map<String, Object> properties = ((MapPropertySource) o).getSource();
                    for (String key : properties.keySet()) {
                        readDatasourceConfig(properties, key);
                    }
                }
            }
        }
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        this.registerDatasource(registry);
    }

    private void registerDatasource(BeanDefinitionRegistry registry) {
        String prefixKey = "lemon.db.";
        if (dataSourceConfigMap.isEmpty()) {
            Set<String> keySet = PropertiesUtils.getPrefixSigns(prefixKey);
            Iterator keyItor = keySet.iterator();
            while (keyItor.hasNext()) {
                // mybatis config
                String key = keyItor.next().toString();
                dbNameSet.add(key);
                String configFilePath = prefixKey + key + ".mybatis.config-file-path";
                String mapperLocations = prefixKey + key + ".mybatis.mapper-locations";
                String mapperBasePackage = prefixKey + key + ".mybatis.mapper-base-package";
                String typeAliasesPackage = prefixKey + key + ".mybatis.type-aliases-package";

                // datasource config
                String driverClassName = prefixKey + key + ".datasource.driverClassName";
                String url = prefixKey + key + ".datasource.url";
                String username = prefixKey + key + ".datasource.username";
                String password = prefixKey + key + ".datasource.password";
                String initialSize = prefixKey + key + ".datasource.initialSize";
                String minIdle = prefixKey + key + ".datasource.minIdle";
                String maxActive = prefixKey + key + ".datasource.maxActive";
                String maxWait = prefixKey + key + ".datasource.maxWait";
                String validationQuery = prefixKey + key + ".datasource.validationQuery";
                String maxPoolPreparedStatementPerConnectionSize = prefixKey + key + ".datasource.maxPoolPreparedStatementPerConnectionSize";
                String filters = prefixKey + key + ".datasource.filters";
                String connectionProperties = prefixKey + key + ".datasource.connectionProperties";

                // mybatis 设置值
                dataSourceConfigMap.put(configFilePath, PropertiesUtils.getString(configFilePath));
                dataSourceConfigMap.put(mapperLocations, PropertiesUtils.getString(mapperLocations));
                dataSourceConfigMap.put(mapperBasePackage, PropertiesUtils.getString(mapperBasePackage));
                dataSourceConfigMap.put(typeAliasesPackage, PropertiesUtils.getString(typeAliasesPackage));

                // datasource 设置值
                dataSourceConfigMap.put(driverClassName, PropertiesUtils.getString(driverClassName));
                dataSourceConfigMap.put(url, PropertiesUtils.getString(url));
                dataSourceConfigMap.put(username, PropertiesUtils.getString(username));
                dataSourceConfigMap.put(password, PropertiesUtils.getString(password));
                dataSourceConfigMap.put(initialSize, StringUtils.isBlank(
                        PropertiesUtils.getString(initialSize)) ? DataSourceDefaultVal.initialSize : PropertiesUtils.getString(initialSize));
                dataSourceConfigMap.put(minIdle, StringUtils.isBlank(
                        PropertiesUtils.getString(minIdle)) ? DataSourceDefaultVal.minIdle : PropertiesUtils.getString(minIdle));
                dataSourceConfigMap.put(maxActive, StringUtils.isBlank(
                        PropertiesUtils.getString(maxActive)) ? DataSourceDefaultVal.maxActive : PropertiesUtils.getString(maxActive));
                dataSourceConfigMap.put(maxWait, StringUtils.isBlank(
                        PropertiesUtils.getString(maxWait)) ? DataSourceDefaultVal.maxWait : PropertiesUtils.getString(maxWait));
                dataSourceConfigMap.put(validationQuery, StringUtils.isBlank(
                        PropertiesUtils.getString(validationQuery)) ? DataSourceDefaultVal.validationQueryMySql : PropertiesUtils.getString(validationQuery));
                dataSourceConfigMap.put(maxPoolPreparedStatementPerConnectionSize, StringUtils.isBlank(
                        PropertiesUtils.getString(maxPoolPreparedStatementPerConnectionSize)) ? DataSourceDefaultVal.maxPoolPreparedStatementPerConnectionSize : PropertiesUtils.getString(maxPoolPreparedStatementPerConnectionSize));
                dataSourceConfigMap.put(filters, StringUtils.isBlank(
                        PropertiesUtils.getString(filters)) ? DataSourceDefaultVal.filters : PropertiesUtils.getString(filters));
                dataSourceConfigMap.put(connectionProperties, StringUtils.isBlank(
                        PropertiesUtils.getString(connectionProperties)) ? DataSourceDefaultVal.connectionProperties : PropertiesUtils.getString(connectionProperties));
            }
        }


        Iterator<String> iterator = dbNameSet.iterator();
        while (iterator.hasNext()) {
            String dbName = iterator.next();
            String prefix = "lemon.db." + dbName + ".";

            RootBeanDefinition dataSourceBean = new RootBeanDefinition();
            dataSourceBean.setBeanClass(DruidDataSource.class);
            // 需要注入的参数，类似spring配置文件中的<property/>
            MutablePropertyValues mpv = dataSourceBean.getPropertyValues();
            // 注入值
            mpv.add("driverClassName", dataSourceConfigMap.get(prefix + "datasource.driverClassName"));
            mpv.add("url", dataSourceConfigMap.get(prefix + "datasource.url"));
            mpv.add("username", dataSourceConfigMap.get(prefix + "datasource.username"));
            mpv.add("password", dataSourceConfigMap.get(prefix + "datasource.password"));
            mpv.add("initialSize", Integer.parseInt(
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.initialSize")) ? DataSourceDefaultVal.initialSize : dataSourceConfigMap.get(prefix + "datasource.initialSize")
            ));
            mpv.add("minIdle", Integer.parseInt(
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.minIdle")) ? DataSourceDefaultVal.minIdle : dataSourceConfigMap.get(prefix + "datasource.minIdle")));
            mpv.add("maxActive", Integer.parseInt(
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.maxActive")) ? DataSourceDefaultVal.maxActive : dataSourceConfigMap.get(prefix + "datasource.maxActive")));
            mpv.add("maxWait", Long.parseLong(
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.maxWait")) ? DataSourceDefaultVal.maxWait : dataSourceConfigMap.get(prefix + "datasource.maxWait")));
            mpv.add("validationQuery",
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.validationQuery")) ? DataSourceDefaultVal.validationQueryMySql : dataSourceConfigMap.get(prefix + "datasource.validationQuery"));
            mpv.add("maxPoolPreparedStatementPerConnectionSize", Integer.parseInt(
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.maxPoolPreparedStatementPerConnectionSize")) ? DataSourceDefaultVal.maxPoolPreparedStatementPerConnectionSize : dataSourceConfigMap.get(prefix + "datasource.maxPoolPreparedStatementPerConnectionSize")));
            mpv.add("filters",
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.filters")) ? DataSourceDefaultVal.filters : dataSourceConfigMap.get(prefix + "datasource.filters"));
            mpv.add("connectionProperties",
                    StringUtils.isBlank(dataSourceConfigMap.get(prefix + "datasource.connectionProperties")) ? DataSourceDefaultVal.connectionProperties : dataSourceConfigMap.get(prefix + "datasource.connectionProperties"));
            if ("default".equals(dbName)) {
                dataSourceBean.setPrimary(true);
            }
            registry.registerBeanDefinition(dbName + "dataSource", dataSourceBean);

            // 添加打印信息
            String protocol = dataSourceConfigMap.get(prefix + "datasource.username") + "/"
                    + dataSourceConfigMap.get(prefix + "datasource.password") + "@"
                    + dataSourceConfigMap.get(prefix + "datasource.url");


            /*RootBeanDefinition paginationInterceptorBean = new RootBeanDefinition();
            paginationInterceptorBean.setBeanClass(PaginationInterceptor.class);
            ConstructorArgumentValues cavs = new ConstructorArgumentValues();
            paginationInterceptorBean.setConstructorArgumentValues(cavs);
            //mpv.add("dialectType", "mysql");
            registry.registerBeanDefinition(dbName + "PaginationInterceptor", paginationInterceptorBean);*/


           /* RootBeanDefinition optimisticLockerInterceptorBean = new RootBeanDefinition();
            optimisticLockerInterceptorBean.setBeanClass(OptimisticLockerInterceptor.class);
            cavs = new ConstructorArgumentValues();
            optimisticLockerInterceptorBean.setConstructorArgumentValues(cavs);
            registry.registerBeanDefinition(dbName + "OptimisticLockerInterceptor", optimisticLockerInterceptorBean);*/

            // 注入 PaginationInterceptor 插件
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            paginationInterceptor.setDialectType("mysql");

            // 注入OptimisticLockerInterceptor 插件
            OptimisticLockerInterceptor optimisticLockerInterceptor = new OptimisticLockerInterceptor();

            // 注入 SessionFactory
            RootBeanDefinition sessionFactoryBean = new RootBeanDefinition();
            sessionFactoryBean.setBeanClass(MybatisSqlSessionFactoryBean.class);
            mpv = sessionFactoryBean.getPropertyValues();
            /*mpv.add("mapperLocations", new PathMatchingResourcePatternResolver()
                    .getResource(dataSourceConfigMap.get(prefix + "mybatis.mapper-locations")));*/
            mpv.add("plugins", new Interceptor[] {paginationInterceptor, optimisticLockerInterceptor});
            mpv.add("dataSource", dataSourceBean);
            mpv.add("typeAliasesPackage", dataSourceConfigMap.get(prefix + "mybatis-plus.type-aliases-package"));
            registry.registerBeanDefinition(dbName + "SessionFactory", sessionFactoryBean);

            // 注入 sqltemplate
            RootBeanDefinition sqlSessionTemplateBean = new RootBeanDefinition();
            sqlSessionTemplateBean.setBeanClass(SqlSessionTemplate.class);
            ConstructorArgumentValues constructorArgs = new ConstructorArgumentValues();
            constructorArgs.addIndexedArgumentValue(0, sessionFactoryBean);
            sqlSessionTemplateBean.setConstructorArgumentValues(constructorArgs);
            registry.registerBeanDefinition(dbName + "SessionTemplate", sqlSessionTemplateBean);

            // 注入 DataSourceTransactionManager
            /*RootBeanDefinition transactionManagerBean = new RootBeanDefinition();
            transactionManagerBean.setBeanClass(DataSourceTransactionManager.class);
            mpv = transactionManagerBean.getPropertyValues();
            mpv.add("dataSource", dataSourceBean);
            registry.registerBeanDefinition(dbName + "TransactionManager", transactionManagerBean);*/


            // 动态指定扫描路径
            ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
            if (resourceLoader != null) { // this check is needed in Spring 3.1
                scanner.setResourceLoader(resourceLoader);
            }
            //scanner.setSqlSessionFactoryBeanName(dbName + "SessionFactory");
            scanner.setSqlSessionTemplateBeanName(dbName + "SessionTemplate");
            scanner.registerFilters();
            scanner.doScan(dataSourceConfigMap.get(prefix + "mybatis.mapper-base-package").split(","));

            // 注入 PerformanceInterceptor
            RootBeanDefinition performanceBean = new RootBeanDefinition();
            performanceBean.setBeanClass(PerformanceInterceptor.class);
            mpv = performanceBean.getPropertyValues();
            mpv.add("maxTime", 1000);
            mpv.add("format", true);
            registry.registerBeanDefinition(dbName + "PerformanceInterceptor", performanceBean);
        }
    }

    private void readDatasourceConfig(Map<String, Object> properties, String key) {
        String redisConfigPrefix = "lemon.db.";
        if (key.contains(redisConfigPrefix)) {
            dataSourceConfigMap.put(key, String.valueOf(properties.get(key)));
            dbNameSet.add(key.replace(redisConfigPrefix, "").substring(0, key.replace(redisConfigPrefix, "").indexOf(".")));
        }
    }
}
